iT邦幫忙

2024 iThome 鐵人賽

DAY 20
1
生成式 AI

使用 Spring AI 打造企業 RAG 知識庫系列 第 20

使用 Spring AI 打造企業 RAG 知識庫【20】- 自行開發Spring AI插件

  • 分享至 

  • xImage
  •  

揭開Advisor面紗

https://ithelp.ithome.com.tw/upload/images/20240820/20161290CbVKKopXpq.png
延續昨天的 Advisor,RequestResponseAdvisor 的設計方式就如同 Filter Chain,每個 Chain 節點都會回傳 Request 與 Response,原始碼如下

▋原始碼說明

public interface RequestResponseAdvisor {
	default AdvisedRequest adviseRequest(AdvisedRequest request, Map<String, Object> context) {
		return request;
	}
	default ChatResponse adviseResponse(ChatResponse response, Map<String, Object> context) {
		return response;
	}
	default Flux<ChatResponse> adviseResponse(Flux<ChatResponse> fluxResponse, Map<String, Object> context) {
		return fluxResponse;
	}
}

要實作 Advisor 非常簡單,主要分成 Request 及 Response,Response 因為有 call 及 stream 流式呼叫,所以提供兩個方法,以下是相關參數說明

  • AdvisedRequest request: 送給 AI 的所有內容都可在這找到,下面就是 AdvisedRequest 帶的參數
public record AdvisedRequest(ChatModel chatModel, String userText, String systemText, ChatOptions chatOptions, List<Media> media, List<String> functionNames, List<FunctionCallback> functionCallbacks, List<Message> messages,	
Map<String, Object> userParams, Map<String, Object> systemParams, List<RequestResponseAdvisor> advisors, Map<String, Object> advisorParams)
  • ChatResponse response: AI 回應的訊息,包含 MetaData 以及 回應的內容
	private final ChatResponseMetadata chatResponseMetadata;
	private final List<Generation> generations;
  • Map<String, Object> context: 貫穿所有 Advisor 的上下文參數,若要讓不同 Advisor 可以共用 request / response 以外的參數,只能透過 context 傳遞

▋程式實作

目標: 寫一隻 Log Advisor,記錄傳送訊息以及使用 Token 數

首先實作 TokenUsageLogAdvistor 類別,在送出 Request 前印出 ChatId 以及傳送的 Message
收到 Response 後從 ChatResponse 取出 AI 生成的內容以及 MetaData,將生成的內容以及花費的 Token 數量印出

@Slf4j
public class TokenUsageLogAdvistor implements RequestResponseAdvisor {
	@Override
	public AdvisedRequest adviseRequest(AdvisedRequest request, Map<String, Object> context) {
		log.info("Chat ID:{} User Message:{}",context.get("chatId"), request.userText());
		return RequestResponseAdvisor.super.adviseRequest(request, context);
	}
	@Override
	public ChatResponse adviseResponse(ChatResponse response, Map<String, Object> context) {
		log.info("Chat ID:{} Assistant Message:{}",context.get("chatId"), response.getResult().getOutput().getContent());
		log.info("PromptTokens:{}",response.getMetadata().getUsage().getPromptTokens());
		log.info("GenerationTokens:{}",response.getMetadata().getUsage().getGenerationTokens());
		log.info("TotalTokens:{}",response.getMetadata().getUsage().getTotalTokens());
		return RequestResponseAdvisor.super.adviseResponse(response, context);
	}
}

在昨天的 ChatService 中加入增強器

@RequiredArgsConstructor
@Service
public class ChatService {
	private final ChatClient chatClient;
	private final ChatMemory chatMemory = new InMemoryChatMemory();
	
	public String chat(String chatId, String userMessage) {	   
		return this.chatClient.prompt()
		.advisors( new MessageChatMemoryAdvisor(chatMemory, chatId, 30), 
		      new TokenUsageLogAdvistor()) //將自訂的 Advistor 附加其後
		.advisors(context -> {context.param("chatId", chatId);
							  context.param("lastN", 30);})
		.user(userMessage)
    .call().content();
	}
}

程式碼重點說明

  • @Slf4j 是 Lombok 提供的標註,可省去撰寫設定 Class 的程式碼,之後直接使用 log 變數就能輸出 log
  • chatId、lastN 並不包含在 request 中,若要讓 log advisor 取得只能透過 context 傳送,傳送參數的程式碼如下
.advisors(context -> {context.param("chatId", chatId);
                      context.param("lastN", 30);})
  • context 是一個 map,只要用下面方式就能取得內容
context.get("chatId")
  • ChatResponse 提供 getMetadata() 可取得資料回傳的額外資訊
  • 一般 AI 的 Token 會區分請求的 Prompt Token 與生成的 Generation Token,兩者費用不太相同
  • 若要進一步控制使用者的使用額度,可以將資料存於資料庫,並在調用前檢查

▋驗收成果

https://ithelp.ithome.com.tw/upload/images/20240820/20161290zCJN7gXZPG.png
可以看到 log 成功輸出,Chat ID 也能夠過 context 取得,另外多了 Token 數量輸出後可以很清楚知道每次發問使用的 Token 數量

▋回顧

今天學到了甚麼?

  • 如何自行開發 Advisor 程式
  • 多個 Advisor 如何串接
  • 額外參數如何透過 context 傳送

RequestResponseAdvisor 的方法皆為 default,不用全部實作

▋Source Code:

程式碼下載: https://github.com/kevintsai1202/SpringBoot-AI-Day20.git


▋認識凱文大叔

凱文大叔使用 Java 開發程式超過 20 年,對於 Java 生態非常熟悉,曾使用反射機制開發 ETL 框架,對 Spring 背後的原理非常清楚,目前以 Spring Boot 作為後端開發框架,前端使用 React 搭配 Ant Design
下班之餘在 Amazing Talker 擔任程式語言講師,並獲得學員的一致好評

最近剛成立一個粉絲專頁-凱文大叔教你寫程式 歡迎大家多追蹤,我會不定期分享實用的知識以及程式開發技巧

想討論 Spring 的 Java 開發人員可以加入 FB 討論區 Spring Boot Developer Taiwan

我是凱文大叔,歡迎一起加入學習程式的行列


上一篇
使用 Spring AI 打造企業 RAG 知識庫【19】- Spring AI的鏈式增強器
下一篇
使用 Spring AI 打造企業 RAG 知識庫【21】- 安裝 Neo4j 向量資料庫(docker)
系列文
使用 Spring AI 打造企業 RAG 知識庫35
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
coder123
iT邦新手 5 級 ‧ 2024-11-15 17:02:02

在spring-ai.version 1.0.0-M3 的時候
RequestResponseAdvisor Interface 已經 deprecated了, 要用 CallAroundAdvisor 去取替吧

public class TokenUsageLogAdvisor implements CallAroundAdvisor {

    @Override
    public AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain) {
        log.info("Chat ID:{} User Message:{}", advisedRequest.adviseContext().get("chatId"), advisedRequest.userText());
        AdvisedResponse advisedResponse = chain.nextAroundCall(advisedRequest);

        ChatResponse response = advisedResponse.response();
        Map<String, Object> responseContext = advisedResponse.adviseContext();
        log.info("Chat ID:{} Assistant Message:{}", responseContext.get("chatId"), response.getResult().getOutput().getContent());
        log.info("PromptTokens:{}", response.getMetadata().getUsage().getPromptTokens());
        log.info("GenerationTokens:{}", response.getMetadata().getUsage().getGenerationTokens());
        log.info("TotalTokens:{}", response.getMetadata().getUsage().getTotalTokens());
        return advisedResponse;
    }

    @Override
    public String getName() {
        return "TokenUsageLogAdvisor";
    }

    @Override
    public int getOrder() { return 1; }
}

沒錯,目前標為deprecated還可使用,不過下一版應該就會拿掉了
Advisor 這一塊調整蠻大的,有時間我再更新

另外 M4 版也快出了,真希望RC前以加入Rerank模型

我要留言

立即登入留言